/*
 *  Copyright (c) 1993 Christopher J. Kane.  All rights reserved.
 *
 *  This software is subject to the terms of the MiscKit license
 *  agreement.  Refer to the license document included with the
 *  MiscKit distribution for these terms.
 *
 *  Version: 1.1.1 (14 December 1993)
 *
 *  Incorporates a bug-fix by Annard Brouwer <annard@stack.urc.tue.nl>.
 *  The nextText outlet of the findTextField was remaining set to the
 *  replaceTextField.  When the replace controls were hidden, this
 *  caused the Tab key in the findTextField to appear to do nothing.
 *  The text in the field is now correctly selected when the Tab key
 *  is pressed.
 */

#import "MiscFindPanel.h"
#import <misckit/SearchableText.h>
#import "Composer.h"
#import "MainWindowControl.h"
#import "descriptors.h"
#import <misckit/MiscAppDefaults.h>

#define DURING		NX_DURING
#define HANDLER		NX_HANDLER switch (NXLocalHandler.code) {
#define ENDHANDLER	default: NXLogError("Uncaught exception: %d," \
			" in %s:%s.\n", NXLocalHandler.code, __FILE__, \
			sel_getName(_cmd)); abort();} NX_ENDHANDLER
#define IGNORE(E)	case E: NXLogError("%s exception in %s:%s. " \
			"Ignored.\n", #E, __FILE__, sel_getName(_cmd)); break

#define LocalString(K)	NXLoadLocalizedStringFromTableInBundle(NULL, \
			[NXBundle bundleForClass:[self class]], K, "")

static MiscFindPanel *_sharedFindPanel=nil;
static Pasteboard *_findPb=nil;
static Object *_firstConformer=nil;
static int _pbChangeCount=0;
static BOOL _replEnabled=YES;
static BOOL _useFindPb=YES;
static BOOL _allocationOK=NO;
static BOOL _resizeOK=NO;
static BOOL _fpLoaded=NO;

#define GSCOPE_TEXT 0
#define GSCOPE_SUBJECT 1
#define GSCOPE_MSGID 2
#define GSCOPE_NEWSGROUPS 3
#define GSCOPE_AUTHOR 4
#define GSCOPE_REFERENCES 5

@interface otherMethods

- newsgroupMatrix;
- articleMatrix;
- mySet;
- (int)grep:(const char *)pattern regexpr:(BOOL)regexpr cases:(BOOL)cases;

@end

@implementation MiscFindPanel


- _calcFindPanelTarget
{
   id d,r=nil;
   id i=[NXApp keyWindow];
   if(i==self)
      i=[NXApp mainWindow];
   
   if((i==nil)||(i==self)) return nil;
   d=[i delegate];
   if(d==nil) return nil;

   if([d isKindOf:[MainWindowControl class]]){
      int scope=[[scopeSelection selectedCell] tag];
      if(scope==GSCOPE_TEXT) 
         r=[d articleText];
      else if(scope==GSCOPE_NEWSGROUPS)
         r=[d newsgroupMatrix];
      else
         r=[d articleMatrix];
   }
   else if([d isKindOf:[Composer class]])
      r=[d theText];

   if([r isKindOf:[Text class]]){
      NXSelPt start,end;

      [r getSel:&start :&end];
      if(start.cp==-1)
         [r setSel:0 :0];
   }

   return r;
}

/*
  id i;
  if (_firstConformer!=nil)
    return _firstConformer;
  if (self!=[NXApp keyWindow])
    {
      for(i=[[NXApp keyWindow] firstResponder]; i; i = [i nextResponder])
        if ([i conformsTo:@protocol(SearchableText)])
          return i;
      i = [[NXApp keyWindow] delegate];
      if ([i conformsTo:@protocol(SearchableText)])
        return i;
    }
  if ([NXApp keyWindow]!=[NXApp mainWindow])
    {
      for(i=[[NXApp mainWindow] firstResponder]; i; i = [i nextResponder])
        if ([i conformsTo:@protocol(SearchableText)])
          return i;
      i = [[NXApp mainWindow] delegate];
      if ([i conformsTo:@protocol(SearchableText)])
        return i;
    }
  i = NXApp;
  if ([i conformsTo:@protocol(SearchableText)])
    return i;
  i = [NXApp delegate];
  if ([i conformsTo:@protocol(SearchableText)])
    return i;
  return nil;
}
*/


- (void)_getFindPbData
{
  if (_pbChangeCount==[_findPb changeCount])
    return;
  DURING
    if ([_findPb findAvailableTypeFrom:&NXAsciiPboardType num:1])
      {
        char *text;
        int len;
        if ([_findPb readType:NXAsciiPboardType data:&text length:&len])
          {
            [findTextField setStringValue:text];
            [_findPb deallocatePasteboardData:text length:len];
          }
      }
  HANDLER
    IGNORE(NX_pasteboardComm);
    IGNORE(NX_appkitVMError);
  ENDHANDLER
  _pbChangeCount = [_findPb changeCount];
}

- (void)_modifyFindPanel
{
  NXRect r;
  [contentView getBounds:&r];
  [self disableFlushWindow];
  if (_replEnabled)
    {
      _resizeOK = YES;
      [super sizeWindow:NX_WIDTH(&r) :NX_HEIGHT(&r)+25.0];
      _resizeOK = NO;
      [[[findTextField superview] superview] moveBy:0.0 :25.0];
      [contentView addSubview:_replBox :NX_BELOW relativeTo:nil];
      [findTextField setNextText:replaceTextField];	/* added by Annard */
    }
  else
    {
      [_replBox removeFromSuperview];
      [[[findTextField superview] superview] moveBy:0.0 :-25.0];
      _resizeOK = YES;
      [super sizeWindow:NX_WIDTH(&r) :NX_HEIGHT(&r)-25.0];
      _resizeOK = NO;
      [findTextField setNextText:findTextField];	/* added by Annard */
    }
  [self display];
  [[self reenableFlushWindow] flushWindowIfNeeded];
}

- (void)_resetFindPanel
{
  [messageTextField setStringValue:""];
  [findTextField selectText:nil];
}

- (void)_setFindPbData
{
  const char *text;
  if (_pbChangeCount==[_findPb changeCount])
    return;
  text = [findTextField stringValue];
  if (*text=='\0')
    return;
  DURING
    _pbChangeCount = [_findPb declareTypes:&NXAsciiPboardType num:1 owner:NULL];
    [_findPb writeType:NXAsciiPboardType data:text length:(int)strlen(text)];
  HANDLER
    IGNORE(NX_pasteboardComm);
  ENDHANDLER
}

+ setFindPbEnabled:(BOOL)aBool
{
  _useFindPb = aBool;
  return self;
}

+ setFirstConformer:aConformer
{
  id formerConformer = _firstConformer;
  if (aConformer==nil || [aConformer conformsTo:@protocol(SearchableText)])
    _firstConformer = aConformer;
  return formerConformer;
}

+ setReplacementEnabled:(BOOL)aBool
{
  if (aBool!=_replEnabled)
    {
      _replEnabled = aBool;
      [_sharedFindPanel _modifyFindPanel];
    }
  return self;
}

+ sharedInstance
{
  if (_sharedFindPanel==nil && !_fpLoaded)
    {
      char path[MAXPATHLEN+16];
      _fpLoaded = YES;
      _findPb = [Pasteboard newName:NXFindPboard];
      _allocationOK = YES;
      if ([[NXBundle bundleForClass:self] getPath:path forResource:"FindPanel.nib" ofType:NULL])
        _sharedFindPanel = [NXApp loadNibFile:path owner:NXApp withNames:NO];
      _allocationOK = NO;
      if (_sharedFindPanel==nil)
        NXRunAlertPanel(LocalString("LOAD_ERR"), LocalString("LOAD_ERR_MSG"), NULL, NULL, NULL);
      else
        {
          if (!_replEnabled)
            [_sharedFindPanel _modifyFindPanel];
          [_sharedFindPanel setExcludedFromWindowsMenu:YES];
          [_sharedFindPanel useOptimizedDrawing:YES];
        }
    }
  return _sharedFindPanel;
}

- enterSelection:sender
{
  [[self _calcFindPanelTarget] writeSelectionToPasteboard:_findPb asType:NXAsciiPboardType];
  [self _getFindPbData];
  [self _resetFindPanel];
  return self;
}

- findBackward:sender
{
  [self _resetFindPanel];
  if (*[findTextField stringValue]=='\0' && _useFindPb)
    [self _getFindPbData];
  if (*[findTextField stringValue]=='\0')
    [self makeKeyAndOrderFront:nil];
  else
    {
      int pos, size, result;
      id target = [self _calcFindPanelTarget];
      if (_useFindPb)
        [self _setFindPbData];
      result = [target searchFor:[findTextField stringValue] mode:SelStartToSelEnd reverse:YES regexpr:[regExprButton state] cases:![ignoreCaseButton state] position:&pos size:&size];
      if (target==nil || result<0)
        {
          [messageTextField setStringValue:LocalString("INVALID_OP")];
          NXBeep();
        }
      else if (result==0)
        {
          [messageTextField setStringValue:LocalString("NOT_FOUND")];
          NXBeep();
        }
      else
        {
          [target selectTextFrom:pos to:pos+size];
          [target makeSelectionVisible];
        }
    }
  return self;
}

- findForward:sender
{
  [self _resetFindPanel];
  if (*[findTextField stringValue]=='\0' && _useFindPb)
    [self _getFindPbData];
  if (*[findTextField stringValue]=='\0')
    [self makeKeyAndOrderFront:nil];
  else
    {
      int pos, size, result;
      id target = [self _calcFindPanelTarget];
      if (_useFindPb)
        [self _setFindPbData];
      result = [target searchFor:[findTextField stringValue] mode:SelEndToSelStart reverse:NO regexpr:[regExprButton state] cases:![ignoreCaseButton state] position:&pos size:&size];
      if (target==nil || result<0)
        {
          [messageTextField setStringValue:LocalString("INVALID_OP")];
          NXBeep();
        }
      else if (result==0)
        {
          [messageTextField setStringValue:LocalString("NOT_FOUND")];
          NXBeep();
        }
      else
        {
          [target selectTextFrom:pos to:pos+size];
          [target makeSelectionVisible];
        }
    }
  return self;
}

- jumpToSelection:sender
{
  [[self _calcFindPanelTarget] makeSelectionVisible];
  return self;
}

- replace:sender
{
  [self _resetFindPanel];
  [[self _calcFindPanelTarget] replaceSelection:[replaceTextField stringValue]];
  return self;
}

- replaceAll:sender
{
  id target;
  int count;
  [self _resetFindPanel];
  if (*[findTextField stringValue]=='\0')
    return self;
  target = [self _calcFindPanelTarget];
  count = [target replaceAll:[findTextField stringValue] with:[replaceTextField stringValue] mode:([scopeMatrix selectedRow]?SelStartToSelEnd:TextEdgeToTextEdge) regexpr:[regExprButton state] cases:![ignoreCaseButton state]];
  if (count<0 || target==nil)
    {
      [messageTextField setStringValue:LocalString("INVALID_OP")];
      NXBeep();
    }
  else
    {
      char buffer[256];
      sprintf(buffer, LocalString("N_REPLACED"), count);
      [messageTextField setStringValue:buffer];
      [target makeSelectionVisible];
    }
  return self;
}

- replaceAndFind:sender
{
  [[self replace:sender] findForward:sender];
  return self;
}

- grep:sender
{
  [self _resetFindPanel];
  if (*[findTextField stringValue]=='\0' && _useFindPb)
    [self _getFindPbData];
  if (*[findTextField stringValue]=='\0')
    [self makeKeyAndOrderFront:nil];
  else
    {
      int result;
      id target = [self _calcFindPanelTarget];
      if (_useFindPb)
        [self _setFindPbData];
      if((![target respondsTo:@selector(mySet)])||
         (![[target mySet] respondsTo:@selector(grep:regexpr:cases:)]))
         target=nil;
      result = [[target mySet] grep:[findTextField stringValue] regexpr:[regExprButton state] cases:![ignoreCaseButton state]];
      if (target==nil || result<0)
        {
          [messageTextField setStringValue:LocalString("INVALID_OP")];
          NXBeep();
        }
      else if (result==0)
        {
          [messageTextField setStringValue:LocalString("NOT_FOUND")];
          NXBeep();
        }
    }
  return self;
}

- gscopeChanged:sender
{
   int sel=[[scopeSelection selectedCell] tag];
   int newDefault=-1;

   switch(sel){
      case GSCOPE_SUBJECT:
         newDefault=SUBJECT;
         break;
      case GSCOPE_MSGID:
         newDefault=MSG_ID;
         break;
      case GSCOPE_AUTHOR:
         newDefault=FROM;
         break;
      case GSCOPE_REFERENCES:
         newDefault=REFS;
         break;
   }

   if(newDefault>=0)
      [NXApp setDefault:"findGeneralScope" toInt:newDefault];

	[replButton1 setEnabled:(sel==GSCOPE_TEXT)];
	[replButton2 setEnabled:(sel==GSCOPE_TEXT)];
	[replButton3 setEnabled:(sel==GSCOPE_TEXT)];
   [replButton4 setEnabled:(sel!=GSCOPE_TEXT)];  	

   return self;
}

- (TextField *)findTextField
{
  return findTextField;
}

- (Button *)ignoreCaseButton
{
  return ignoreCaseButton;
}

- (TextField *)messageTextField
{
  return messageTextField;
}

- (Button *)regExprButton
{
  return regExprButton;
}

- (TextField *)replaceTextField
{
  return replaceTextField;
}

- (Matrix *)scopeMatrix
{
  return scopeMatrix;
}

- (Matrix *)scopeSelection
{
   return scopeSelection;
}

- textDidEnd:sender endChar:(unsigned short)theChar
{
  if (theChar==NX_RETURN)
    [self orderOut:nil];
  return self;
}

- (BOOL)textWillChange:sender
{
  return NO;
}

- (BOOL)textWillEnd:sender
{
  return NO;
}

+ (BOOL)_canAlloc
{
  return _allocationOK;
}

+ alloc
{
  if (!_allocationOK)
    return [self doesNotRecognize:_cmd];
  return class_createInstanceFromZone(self, 0, NXDefaultMallocZone());
}

+ allocFromZone:(NXZone *)zone
{
  if (!_allocationOK)
    return [self doesNotRecognize:_cmd];
  if (zone!=NX_NOZONE)
    return class_createInstanceFromZone(self, 0, zone);
  return nil;
}

+ (BOOL)instancesRespondTo:(SEL)aSel
{
  if (aSel==@selector(copy) ||
      aSel==@selector(copyFromZone:) ||
      aSel==@selector(free) ||
      aSel==@selector(init) ||
      aSel==@selector(initContent:style:backing:buttonMask:defer:) ||
      aSel==@selector(initContent:style:backing:buttonMask:defer:screen:) ||
      aSel==@selector(miniaturize:) ||
      aSel==@selector(placeWindow:) ||
      aSel==@selector(placeWindow:screen:) ||
      aSel==@selector(placeWindowAndDisplay:) ||
      aSel==@selector(setDocEdited:) ||
      aSel==@selector(sizeWindow::))
    return NO;
  return [super instancesRespondTo:aSel];
}

+ new
{
  return [self doesNotRecognize:_cmd];
}

+ newContent:(const NXRect *)contentRect style:(int)aStyle backing:(int)bufferingType buttonMask:(int)mask defer:(BOOL)flag
{
  return [self doesNotRecognize:_cmd];
}

+ newContent:(const NXRect *)contentRect style:(int)aStyle backing:(int)bufferingType buttonMask:(int)mask defer:(BOOL)flag screen:(const NXScreen *)screen
{
  return [self doesNotRecognize:_cmd];
}

- copyFromZone:(NXZone *)zone
{
  return self;
}

- free
{
  return self;
}

- init
{
  return [self doesNotRecognize:_cmd];
}

- initContent:(const NXRect *)contentRect style:(int)aStyle backing:(int)bufferingType buttonMask:(int)mask defer:(BOOL)flag
{
  if (!_allocationOK)
    return [self doesNotRecognize:_cmd];
  return [super initContent:contentRect style:aStyle backing:bufferingType buttonMask:mask defer:flag];
}

- initContent:(const NXRect *)contentRect style:(int)aStyle backing:(int)bufferingType buttonMask:(int)mask defer:(BOOL)flag screen:(const NXScreen *)screen
{
  return [self doesNotRecognize:_cmd];
}

- miniaturize:sender
{
  return self;
}

- orderWindow:(int)place relativeTo:(int)otherWin
{
  if (place!=NX_OUT)
    {
      if (_useFindPb)
        [self _getFindPbData];
      [self _resetFindPanel];
    }
  return [super orderWindow:place relativeTo:otherWin];
}

- placeWindow:(const NXRect *)aRect
{
  if (_resizeOK)
    return [super placeWindow:aRect];
  return [self moveTo:NX_X(aRect) :NX_Y(aRect)];
}

- placeWindow:(const NXRect *)aRect screen:(const NXScreen *)aScreen
{
  if (_resizeOK)
    return [super placeWindow:aRect screen:aScreen];
  return [self moveTo:NX_X(aRect) :NX_Y(aRect) screen:aScreen];
}

- placeWindowAndDisplay:(const NXRect *)aRect
{
  if (_resizeOK)
    return [super placeWindowAndDisplay:aRect];
  return [[self moveTo:NX_X(aRect) :NX_Y(aRect)] display];
}

- read:(NXTypedStream *)stream
{
  [super read:stream];
  findTextField = NXReadObject(stream);
  replaceTextField = NXReadObject(stream);
  messageTextField = NXReadObject(stream);
  ignoreCaseButton = NXReadObject(stream);
  regExprButton = NXReadObject(stream);
  scopeMatrix = NXReadObject(stream);
  _replBox = NXReadObject(stream);
  return self;
}

- (BOOL)respondsTo:(SEL)aSel
{
  if (aSel==@selector(alloc) ||
      aSel==@selector(allocFromZone:) ||
      aSel==@selector(new) ||
      aSel==@selector(newContent:style:backing:buttonMask:defer:) ||
      aSel==@selector(newContent:style:backing:buttonMask:defer:screen:) ||
      aSel==@selector(copy) ||
      aSel==@selector(copyFromZone:) ||
      aSel==@selector(free) ||
      aSel==@selector(init) ||
      aSel==@selector(initContent:style:backing:buttonMask:defer:) ||
      aSel==@selector(initContent:style:backing:buttonMask:defer:screen:) ||
      aSel==@selector(miniaturize:) ||
      aSel==@selector(placeWindow:) ||
      aSel==@selector(placeWindow:screen:) ||
      aSel==@selector(placeWindowAndDisplay:) ||
      aSel==@selector(setDocEdited:) ||
      aSel==@selector(sizeWindow::))
    return NO;
  return [super respondsTo:aSel];
}

- setDocEdited:(BOOL)aBool
{
  return self;
}

- sizeWindow:(float)x :(float)y
{
  return self;
}

- write:(NXTypedStream *)stream
{
  [super write:stream];
  NXWriteObjectReference(stream, findTextField);
  NXWriteObjectReference(stream, replaceTextField);
  NXWriteObjectReference(stream, messageTextField);
  NXWriteObjectReference(stream, ignoreCaseButton);
  NXWriteObjectReference(stream, regExprButton);
  NXWriteObjectReference(stream, scopeMatrix);
  NXWriteObjectReference(stream, _replBox);
  return self;
}

@end
